/*
Copyright 2019-2024 New Vector Ltd.
Copyright 2017 Vector Creations Ltd

SPDX-License-Identifier: AGPL-3.0-only
Please see LICENSE in the repository root for full details.
 */

#import "IntegrationManagerViewController.h"

#import "WidgetManager.h"

#import "GeneratedInterface-Swift.h"

NSString *const kIntegrationManagerMainScreen = nil;
NSString *const kIntegrationManagerAddIntegrationScreen = @"add_integ";


@interface IntegrationManagerViewController () <ServiceTermsModalCoordinatorBridgePresenterDelegate>
{
    MXSession *mxSession;
    NSString *roomId;
    NSString *screen;
    NSString *widgetId;
    NSString *scalarToken;

    MXHTTPOperation *operation;
}

@property (nonatomic, strong) ServiceTermsModalCoordinatorBridgePresenter *serviceTermsModalCoordinatorBridgePresenter;
@property (nonatomic) BOOL isViewAppearedOnce;

@end

@implementation IntegrationManagerViewController

- (instancetype)initForMXSession:(MXSession *)theMXSession inRoom:(NSString *)theRoomId screen:(NSString *)theScreen widgetId:(NSString *)theWidgetId
{
    self = [super init];
    if (self)
    {
        mxSession = theMXSession;
        roomId = theRoomId;
        screen = theScreen;
        widgetId = theWidgetId;
    }
    return self;
}

+ (NSString*)screenForWidget:(NSString*)widgetType
{
    return [NSString stringWithFormat:@"type_%@", widgetType];
}

- (void)destroy
{
    [super destroy];

    [operation cancel];
    operation = nil;
}

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
    
    if (!self.isViewAppearedOnce)
    {
        self.isViewAppearedOnce = YES;
        [self loadData];
    }
}

- (void)loadData
{
    RiotSharedSettings *sharedSettings = [[RiotSharedSettings alloc] initWithSession:mxSession];
    if (!sharedSettings.hasIntegrationProvisioningEnabled)
    {
        [self showDisabledIntegrationManagerError];
        return;
    }

    if (!self.URL && !operation)
    {
        [self startActivityIndicator];

        // Make sure we have a scalar token
        MXWeakify(self);
        operation = [[WidgetManager sharedManager] getScalarTokenForMXSession:mxSession validate:YES  success:^(NSString *theScalarToken) {
            MXStrongifyAndReturnIfNil(self);

            self->operation = nil;
            self->scalarToken = theScalarToken;

            // Launch the webview on the right modular webapp page
            self.URL = [self interfaceUrl];

        } failure:^(NSError *error) {
            MXStrongifyAndReturnIfNil(self);

            MXLogDebug(@"[IntegraionManagerVS] Cannot open due to missing scalar token. Error: %@", error);

            self->operation = nil;
            [self stopActivityIndicator];

            if ([error.domain isEqualToString:WidgetManagerErrorDomain]
                && error.code == WidgetManagerErrorCodeTermsNotSigned)
            {
                [self presentTerms];
            }
            else
            {
                [self withdrawViewControllerAnimated:YES completion:^{
                    [[AppDelegate theDelegate] showErrorAsAlert:error];
                }];
            }
        }];
    }
}

#pragma mark - Private methods

/**
 Build the URL to use in the Modular interface webapp.
 */
- (NSString *)interfaceUrl
{
    NSMutableString *url;

    NSString *integrationsUiUrl = [[WidgetManager sharedManager] configForUser:mxSession.myUser.userId].uiUrl;

    if (scalarToken)
    {
        url = [NSMutableString stringWithFormat:@"%@?scalar_token=%@&room_id=%@",
               integrationsUiUrl,
               [MXTools encodeURIComponent:scalarToken],
               [MXTools encodeURIComponent:roomId]
               ];

        if (screen)
        {
            [url appendString:@"&screen="];
            [url appendString:[MXTools encodeURIComponent:screen]];
        }

        if (widgetId)
        {
            [url appendString:@"&integ_id="];
            [url appendString:[MXTools encodeURIComponent:widgetId]];
        }
    }
    
    return url;
}

#pragma mark - Modular postMessage API implementation

- (void)onPostMessageRequest:(NSString*)requestId data:(NSDictionary*)requestData
{
    NSString *roomIdInEvent, *userId, *action;

    MXJSONModelSetString(roomIdInEvent, requestData[@"room_id"]);
    MXJSONModelSetString(userId, requestData[@"user_id"]);
    MXJSONModelSetString(action, requestData[@"action"]);

    if ([action isEqualToString:@"close_scalar"])
    {
        [self withdrawViewControllerAnimated:YES completion:nil];
        return;
    }

    if (!roomIdInEvent)
    {
        // These APIs don't require roomId
        // Get and set user widgets (not associated with a specific room)
        // If roomId is specified, it must be validated, so room-based widgets agreed
        // handled further down.
        if ([@"set_widget" isEqualToString:action])
        {
            [self setWidget:requestId data:requestData];
            return;
        }
        else if ([@"get_widgets" isEqualToString:action])
        {
            [self getWidgets:requestId data:requestData];
            return;
        }
        else
        {
            [self sendError:[VectorL10n widgetIntegrationMissingRoomId] toRequest:requestId];
            return;
        }
    }

    if (![roomIdInEvent isEqualToString:roomId])
    {
        [self sendError:[VectorL10n widgetIntegrationRoomNotVisible:roomIdInEvent] toRequest:requestId];
        return;
    }

    // Get and set room-based widgets
    if ([@"set_widget" isEqualToString:action])
    {
        [self setWidget:requestId data:requestData];
        return;
    }
    else if ([@"get_widgets" isEqualToString:action])
    {
        [self getWidgets:requestId data:requestData];
        return;
    }

    // These APIs don't require userId
    if ([@"join_rules_state" isEqualToString:action])
    {
        [self getJoinRules:requestId data:requestData];
        return;
    }
    else if ([@"set_plumbing_state" isEqualToString:action])
    {
        [self setPlumbingState:requestId data:requestData];
        return;
    }
    else if ([@"get_membership_count" isEqualToString:action])
    {
        [self getMembershipCount:requestId data:requestData];
        return;
    }
    else if ([@"get_room_enc_state" isEqualToString:action])
    {
        [self getRoomEncState:requestId data:requestData];
        return;
    }
    else if ([@"can_send_event" isEqualToString:action])
    {
        [self canSendEvent:requestId data:requestData];
        return;
    }


    if (!userId)
    {
        [self sendError:[VectorL10n widgetIntegrationMissingUserId] toRequest:requestId];
        return;
    }

    if ([@"membership_state" isEqualToString:action])
    {
        [self getMembershipState:userId request:requestId data:requestData];
    }
    else if ([@"invite" isEqualToString:action])
    {
        [self inviteUser:userId request:requestId data:requestData];
    }
    else if ([@"bot_options" isEqualToString:action])
    {
        [self getBotOptions:userId request:requestId data:requestData];
    }
    else if ([@"set_bot_options" isEqualToString:action])
    {
        [self setBotOptions:userId request:requestId data:requestData];
    }
    else if ([@"set_bot_power" isEqualToString:action])
    {
        [self setBotPower:userId request:requestId data:requestData];
    }
    else
    {
        MXLogDebug(@"[IntegrationManagerViewControllerVC] Unhandled postMessage event with action %@: %@", action, requestData);
    }
}

#pragma mark - Private methods

- (void)roomCheckForRequest:(NSString*)requestId data:(NSDictionary*)requestData onComplete:(void (^)(MXRoom *room, MXRoomState *roomState))onComplete
{
    MXRoom *room = [mxSession roomWithRoomId:roomId];
    if (room)
    {
        [room state:^(MXRoomState *roomState) {
            onComplete(room, roomState);
        }];
    }
    else
    {
        [self sendError:[VectorL10n widgetIntegrationRoomNotRecognised] toRequest:requestId];
    }
}

- (void)inviteUser:(NSString*)userId request:(NSString*)requestId data:(NSDictionary*)requestData
{
    MXLogDebug(@"[IntegrationManagerVC] Received request to invite %@ into room %@.", userId, roomId);

    [self roomCheckForRequest:requestId data:requestData onComplete:^(MXRoom *room, MXRoomState *roomState) {

        MXRoomMember *member = [roomState.members memberWithUserId:userId];
        if (member && member.membership == MXMembershipJoin)
        {
            [self sendNSObjectResponse:@{
                                         @"success": @(YES)
                                         }
                               toRequest:requestId];
        }
        else
        {
            MXWeakify(self);
            [room inviteUser:userId success:^{
                MXStrongifyAndReturnIfNil(self);

                [self sendNSObjectResponse:@{
                                             @"success": @(YES)
                                             }
                                 toRequest:requestId];

            } failure:^(NSError *error) {
                MXStrongifyAndReturnIfNil(self);

                [self sendError:[VectorL10n widgetIntegrationNeedToBeAbleToInvite] toRequest:requestId];
            }];
        }
    }];
}

- (void)setWidget:(NSString*)requestId data:(NSDictionary*)requestData
{
    MXLogDebug(@"[IntegrationManagerVC] Received request to set widget");

    NSString *widget_id, *widgetType, *widgetUrl;
    NSString *widgetName; // optional
    NSDictionary *widgetData ; // optional
    BOOL userWidget = NO;

    MXJSONModelSetString(widget_id, requestData[@"widget_id"]);
    MXJSONModelSetString(widgetType, requestData[@"type"]);
    MXJSONModelSetString(widgetUrl, requestData[@"url"]);
    MXJSONModelSetString(widgetName, requestData[@"name"]);
    MXJSONModelSetDictionary(widgetData, requestData[@"data"]);
    MXJSONModelSetBoolean(userWidget, requestData[@"userWidget"]);

    if (!widget_id)
    {
        [self sendError:[VectorL10n widgetIntegrationUnableToCreate] toRequest:requestId];
        return;
    }

    if (!widgetType)
    {
        [self sendError:[VectorL10n widgetIntegrationUnableToCreate] toRequest:requestId];
        return;
    }

    NSMutableDictionary *widgetEventContent = [NSMutableDictionary dictionary];
    if (widgetUrl)
    {
        widgetEventContent[@"type"] = widgetType;
        widgetEventContent[@"url"] = widgetUrl;

        if (widgetName)
        {
            widgetEventContent[@"name"] = widgetName;
        }
        if (widgetData)
        {
            widgetEventContent[@"data"] = widgetData;
        }
    }
    // else this is a deletion

    __weak __typeof__(self) weakSelf = self;

    if (userWidget)
    {
        // Update the user account data
        NSMutableDictionary *userWidgets = [NSMutableDictionary dictionaryWithDictionary:[mxSession.accountData accountDataForEventType:kMXAccountDataTypeUserWidgets]];

        // Delete existing widget with ID
        [userWidgets removeObjectForKey:widget_id];

        // Add new widget / update
        if (widgetUrl)
        {
            userWidgets[widget_id] = @{
                                      @"content": widgetEventContent,
                                      @"sender": mxSession.myUser.userId,
                                      @"state_key": widget_id,
                                      @"type": kWidgetMatrixEventTypeString,
                                      @"id": widget_id,
                                      };
        }

        [mxSession setAccountData:userWidgets forType:kMXAccountDataTypeUserWidgets success:^{

            typeof(self) self = weakSelf;
            if (self)
            {
                [self sendNSObjectResponse:@{
                                             @"success": @(YES)
                                             }
                                 toRequest:requestId];
            }
        } failure:^(NSError *error) {

            typeof(self) self = weakSelf;
            if (self)
            {
                [self sendError:[VectorL10n widgetIntegrationUnableToCreate] toRequest:requestId];
            }
        }];
    }
    else
    {
        // Room widget
        [self roomCheckForRequest:requestId data:requestData onComplete:^(MXRoom *room, MXRoomState *roomState) {

            // TODO: Move to kWidgetMatrixEventTypeString ("m.widget") type but when?
            [room sendStateEventOfType:kWidgetModularEventTypeString
                               content:widgetEventContent
                              stateKey:widget_id
                               success:^(NSString *eventId) {

                                   typeof(self) self = weakSelf;
                                   if (self)
                                   {
                                       [self sendNSObjectResponse:@{
                                                                    @"success": @(YES)
                                                                    }
                                                        toRequest:requestId];
                                   }
                               }
                               failure:^(NSError *error) {

                                   typeof(self) self = weakSelf;
                                   if (self)
                                   {
                                       [self sendError:[VectorL10n widgetIntegrationFailedToSendRequest] toRequest:requestId];
                                   }
                               }];
        }];
    }
}

- (void)getWidgets:(NSString*)requestId data:(NSDictionary*)requestData
{
    MXWeakify(self);
    [self roomCheckForRequest:requestId data:requestData onComplete:^(MXRoom *room, MXRoomState *roomState) {
        MXStrongifyAndReturnIfNil(self);

        NSMutableArray<NSDictionary*> *widgetStateEvents = [NSMutableArray array];

        NSArray<Widget*> *widgets = [[WidgetManager sharedManager] widgetsInRoom:room withRoomState:roomState];
        for (Widget *widget in widgets)
        {
            [widgetStateEvents addObject:widget.widgetEvent.JSONDictionary];
        }

        // Add user widgets (not linked to a specific room)
        for (Widget *widget in [[WidgetManager sharedManager] userWidgets:self->mxSession])
        {
            [widgetStateEvents addObject:widget.widgetEvent.JSONDictionary];
        }

        [self sendNSObjectResponse:widgetStateEvents toRequest:requestId];
    }];
}

- (void)getRoomEncState:(NSString*)requestId data:(NSDictionary*)requestData
{
    [self roomCheckForRequest:requestId data:requestData onComplete:^(MXRoom *room, MXRoomState *roomState) {
        [self sendBoolResponse:room.summary.isEncrypted toRequest:requestId];
    }];
}

- (void)canSendEvent:(NSString*)requestId data:(NSDictionary*)requestData
{
    MXWeakify(self);
    [self roomCheckForRequest:requestId data:requestData onComplete:^(MXRoom *room, MXRoomState *roomState) {
        MXStrongifyAndReturnIfNil(self);

        NSString *eventType;
        BOOL isState = NO;

        if (room.summary.membership != MXMembershipJoin)
        {
            [self sendError:[VectorL10n widgetIntegrationMustBeInRoom] toRequest:requestId];
            return;
        }

        MXJSONModelSetString(eventType, requestData[@"event_type"]);
        MXJSONModelSetBoolean(isState, requestData[@"is_state"]);

        MXRoomPowerLevels *powerLevels = roomState.powerLevels;
        NSInteger userPowerLevel = [roomState powerLevelOfUserWithUserID:self->mxSession.myUser.userId];

        BOOL canSend = NO;

        if (isState)
        {
            canSend = (userPowerLevel >= [powerLevels minimumPowerLevelForSendingEventAsStateEvent:eventType]);
        }
        else
        {
            canSend = (userPowerLevel >= [powerLevels minimumPowerLevelForSendingEventAsMessage:eventType]);
        }

        if (canSend)
        {
            [self sendBoolResponse:YES toRequest:requestId];
        }
        else
        {
            [self sendError:[VectorL10n widgetIntegrationNoPermissionInRoom] toRequest:requestId];
        }

    }];
}

- (void)getMembershipState:(NSString*)userId request:(NSString*)requestId data:(NSDictionary*)requestData
{
    MXLogDebug(@"[IntegrationManagerVC] membership_state of %@ in room %@ requested.", userId, roomId);

    [self roomCheckForRequest:requestId data:requestData onComplete:^(MXRoom *room, MXRoomState *roomState) {
        MXRoomMember *member = [roomState.members memberWithUserId:userId];
        [self sendNSObjectResponse:member.originalEvent.content toRequest:requestId];
    }];
}

- (void)getJoinRules:(NSString*)requestId data:(NSDictionary*)requestData
{
    MXLogDebug(@"[IntegrationManagerVC] join_rules of %@ requested.", roomId);

    [self roomCheckForRequest:requestId data:requestData onComplete:^(MXRoom *room, MXRoomState *roomState) {
        MXEvent *event = [roomState stateEventsWithType:kMXEventTypeStringRoomJoinRules].lastObject;
        [self sendNSObjectResponse:event.JSONDictionary toRequest:requestId];
    }];
}

- (void)setPlumbingState:(NSString*)requestId data:(NSDictionary*)requestData
{
    MXLogDebug(@"[IntegrationManagerVC] Received request to set plumbing state to status %@ in room %@.", requestData[@"status"], roomId);

    [self roomCheckForRequest:requestId data:requestData onComplete:^(MXRoom *room, MXRoomState *roomState) {
        NSString *status;
        MXJSONModelSetString(status, requestData[@"status"]);

        if (status)
        {
            __weak __typeof__(self) weakSelf = self;

            [room sendStateEventOfType:kMXEventTypeStringRoomPlumbing
                               content:@{
                                         @"status": status
                                         }
                              stateKey:nil
                               success:^(NSString *eventId) {

                                   typeof(self) self = weakSelf;
                                   if (self)
                                   {
                                       [self sendNSObjectResponse:@{
                                                                    @"success": @(YES)
                                                                    }
                                                        toRequest:requestId];
                                   }
                               }
                               failure:^(NSError *error) {

                                   typeof(self) self = weakSelf;
                                   if (self)
                                   {
                                       [self sendError:[VectorL10n widgetIntegrationFailedToSendRequest] toRequest:requestId];
                                   }
                               }];
        }
        else
        {
            MXLogDebug(@"[IntegrationManagerVC] setPlumbingState. Error: Plumbing state status should be a string.");
        }

    }];
}

- (void)getBotOptions:(NSString*)userId request:(NSString*)requestId data:(NSDictionary*)requestData
{
    MXLogDebug(@"[IntegrationManagerVC] Received request to get options for bot %@ in room %@", userId, roomId);

    [self roomCheckForRequest:requestId data:requestData onComplete:^(MXRoom *room, MXRoomState *roomState) {
        NSString *stateKey = [NSString stringWithFormat:@"_%@", userId];

        NSArray<MXEvent*> *stateEvents = [roomState stateEventsWithType:kMXEventTypeStringRoomBotOptions];

        MXEvent *botOptionsEvent;

        for (MXEvent *stateEvent in stateEvents)
        {
            if ([stateEvent.stateKey isEqualToString:stateKey])
            {
                if (!botOptionsEvent || stateEvent.ageLocalTs > botOptionsEvent.ageLocalTs)
                {
                    botOptionsEvent = stateEvent;
                }
            }
        }

        [self sendNSObjectResponse:botOptionsEvent.JSONDictionary toRequest:requestId];

    }];
}

- (void)setBotOptions:(NSString*)userId request:(NSString*)requestId data:(NSDictionary*)requestData
{
    MXLogDebug(@"[IntegrationManagerVC] Received request to set options for bot %@ in room %@", userId, roomId);

    [self roomCheckForRequest:requestId data:requestData onComplete:^(MXRoom *room, MXRoomState *roomState) {
        NSDictionary *content;
        MXJSONModelSetDictionary(content, requestData[@"content"]);

        if (content)
        {
            __weak __typeof__(self) weakSelf = self;

            NSString *stateKey = [NSString stringWithFormat:@"_%@", userId];

            [room sendStateEventOfType:kMXEventTypeStringRoomBotOptions
                               content:content
                              stateKey:stateKey
                               success:^(NSString *eventId) {

                                   typeof(self) self = weakSelf;
                                   if (self)
                                   {
                                       [self sendNSObjectResponse:@{
                                                                    @"success": @(YES)
                                                                    }
                                                        toRequest:requestId];
                                   }
                               }
                               failure:^(NSError *error) {

                                   typeof(self) self = weakSelf;
                                   if (self)
                                   {
                                       [self sendError:[VectorL10n widgetIntegrationFailedToSendRequest] toRequest:requestId];
                                   }
                               }];
        }
        else
        {
            MXLogDebug(@"[IntegrationManagerVC] setBotOptions. Error: options should be a dict.");
        }
    }];
}

- (void)setBotPower:(NSString*)userId request:(NSString*)requestId data:(NSDictionary*)requestData
{
    MXLogDebug(@"[IntegrationManagerVC] Received request to set power level to %@ for bot %@ in room %@.", requestData[@"level"], userId, roomId);

    [self roomCheckForRequest:requestId data:requestData onComplete:^(MXRoom *room, MXRoomState *roomState) {
        NSInteger level = -1;
        MXJSONModelSetInteger(level, requestData[@"level"]);

        if (level >= 0)
        {
            __weak __typeof__(self) weakSelf = self;

            [room setPowerLevelOfUserWithUserID:userId powerLevel:level success:^{

                typeof(self) self = weakSelf;
                if (self)
                {
                    [self sendNSObjectResponse:@{
                                                 @"success": @(YES)
                                                 }
                                     toRequest:requestId];
                }

            } failure:^(NSError *error) {

                typeof(self) self = weakSelf;
                if (self)
                {
                    [self sendError:[VectorL10n widgetIntegrationFailedToSendRequest] toRequest:requestId];
                }
            }];
        }
        else
        {
            MXLogDebug(@"[IntegrationManagerVC] setBotPower. Power level must be positive integer.");
            [self sendError:[VectorL10n widgetIntegrationPositivePowerLevel] toRequest:requestId];
        }
    }];
}

- (void)getMembershipCount:(NSString*)requestId data:(NSDictionary*)requestData
{
    [self roomCheckForRequest:requestId data:requestData onComplete:^(MXRoom *room, MXRoomState *roomState) {
        NSUInteger membershipCount = room.summary.membersCount.joined;
        [self sendIntegerResponse:membershipCount toRequest:requestId];
    }];
}


#pragma mark - Widget Permission

- (void)checkWidgetPermissionWithCompletion:(void (^)(BOOL granted))completion
{
    // The integration manager widget has its own terms
    completion(YES);
}


#pragma mark - Disabled Integrations

- (void)showDisabledIntegrationManagerError
{
    NSString *message = [VectorL10n widgetIntegrationManagerDisabled];
    UIAlertController *alert = [UIAlertController alertControllerWithTitle:nil
                                                                   message:message
                                                            preferredStyle:UIAlertControllerStyleAlert];

    [alert addAction:[UIAlertAction actionWithTitle:[VectorL10n ok]
                                              style:UIAlertActionStyleDefault
                                            handler:^(UIAlertAction * action) {
                                                [self withdrawViewControllerAnimated:YES completion:nil];
                                            }]];

    [self presentViewController:alert animated:YES completion:nil];
}

#pragma mark - Service terms

- (void)presentTerms
{
    WidgetManagerConfig *config =  [[WidgetManager sharedManager] configForUser:mxSession.myUser.userId];

    MXLogDebug(@"[IntegrationManagerVC] presentTerms for %@", config.baseUrl);

    ServiceTermsModalCoordinatorBridgePresenter *serviceTermsModalCoordinatorBridgePresenter = [[ServiceTermsModalCoordinatorBridgePresenter alloc] initWithSession:mxSession baseUrl:config.baseUrl
                                                                                                                                                        serviceType:MXServiceTypeIntegrationManager
                                                                                                                                                        accessToken:config.scalarToken];

    serviceTermsModalCoordinatorBridgePresenter.delegate = self;

    [serviceTermsModalCoordinatorBridgePresenter presentFrom:self animated:YES];
    self.serviceTermsModalCoordinatorBridgePresenter = serviceTermsModalCoordinatorBridgePresenter;
}

- (void)serviceTermsModalCoordinatorBridgePresenterDelegateDidAccept:(ServiceTermsModalCoordinatorBridgePresenter * _Nonnull)coordinatorBridgePresenter
{
    [coordinatorBridgePresenter dismissWithAnimated:YES completion:^{
        [self loadData];
    }];
    self.serviceTermsModalCoordinatorBridgePresenter = nil;
}

- (void)serviceTermsModalCoordinatorBridgePresenterDelegateDidDecline:(ServiceTermsModalCoordinatorBridgePresenter * _Nonnull)coordinatorBridgePresenter session:(MXSession * _Nonnull)session
{
    [coordinatorBridgePresenter dismissWithAnimated:YES completion:^{
        [self withdrawViewControllerAnimated:YES completion:nil];
    }];
    self.serviceTermsModalCoordinatorBridgePresenter = nil;
}

- (void)serviceTermsModalCoordinatorBridgePresenterDelegateDidClose:(ServiceTermsModalCoordinatorBridgePresenter * _Nonnull)coordinatorBridgePresenter
{
    self.serviceTermsModalCoordinatorBridgePresenter = nil;
}

@end
